#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "service.h"

#include "server.h"


int com_socket(service_t* serv);
void close_socks(service_t* head);


/**
 *  PREPARE THE COMMUNICATION SOCKET
**/
int com_socket(service_t* serv) {
  char buff;
  int com_sock;

  if (serv->transport == TCP) {

    /* TCP SERVER */
    int opt = 1;

    /* accept() della richiesta di connessione */
    do {
      errno = 0;
      com_sock = accept(serv->sock, NULL, NULL);
    } while (errno == EINTR);
    if (errno != 0) {
      err(EXIT_FAILURE, "on accept(%d, ..) for %s",
          serv->sock, service_to_str(serv));
    }

    /* abilitazione del timer keepalive per il socket di comunicazione */
    do {
      errno = 0;
      setsockopt(serv->sock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
    } while (errno == EINTR);
    if (errno != 0) {
      err(EXIT_FAILURE, "on setsockopt(%d, .., SO_KEEPALIVE, 1, ..) for %s",
          serv->sock, service_to_str(serv));
    }
  }

  /* UDP SERVER */
  else {
    struct sockaddr_in client_addr;
    socklen_t cli_addr_len;
    int n_rec;

    com_sock = serv->sock;

    /* reperimento dell'indirizzo client (mediante recvfrom() ) */
    cli_addr_len = sizeof(client_addr);
    do {
      errno = 0;
      n_rec = recvfrom(serv->sock, &buff, 1, MSG_PEEK,
             (struct sockaddr*)&client_addr, &cli_addr_len);
    } while (errno == EINTR);
    if (errno != 0) {
      err(EXIT_FAILURE, "on recvfrom(%d, ..) for %s", serv->sock, service_to_str(serv));
    }

    /* "connessione" del socket UDP al client */
    do {
      errno = 0;
      connect(com_sock, (struct sockaddr*)&client_addr, sizeof(client_addr));
    } while (errno == EINTR);
    if (errno != 0) {
      err(EXIT_FAILURE, "on connect(%d, ..) for %s", com_sock, service_to_str(serv));
    }
  }
  return com_sock;
}


/**
 *  LAUNCH THE SERVER
**/
void launch_server(service_t *head, service_t* serv) {
  pid_t pid;
  int com_sock;
  char* serv_str;//debug

  serv_str = service_to_str(serv);//debug

  com_sock = com_socket(serv);




  /* fork */
  do {
    errno = 0;
    pid = fork();
  } while (errno == EINTR);
  if (pid < 0) {
    err(EXIT_FAILURE, "on fork() for %s", service_to_str(serv));
  }

  /* PARENT */
  if (pid > 0) {
    /* chiusura del socket TCP di comunicazione da parte del processo *
     * padre;                                                         *
     * in caso di socket UDP il socket di comunicazione coincide con  *
     * quello di connessione, percio' non viene chiuso (viene solo    *
     * escluso dalle select() )                                       */

    /* setting del pid per servizi TCP WAIT e UDP */
    if ( (serv->concurr == WAIT) || (serv->transport == UDP)) {
      /* BUG - Il figlio potrebbe terminare prima che questa istruzione venga *
       * eseguita. Cio' rende impossibile la reintroduzione del socket nell'  *
       * fd set, e inaccessibile il relativo servizio TCP-sequenziale o UDP.  *
       * Occorre IPC (es. segnale).                                           */
      serv->pid = pid;
      fprintf(stderr, "[%d] spawned (%s -> BUSY)\n", pid, serv_str);// debug
    }
    else { // debug
      fprintf(stderr, "[%d] spawned (%s)\n", pid, serv_str);// debug
    } // debug

    if (serv->transport == TCP) {
      close(com_sock);
    }
  }

  /* CHILD */
  else {

    /* dup() del socket di comunicazione su stdin, stdout, e stderr */
    if ((dup2(com_sock, 0) != 0) ||
        (dup2(com_sock, 1) != 1)/* || TEMP
        (dup2(com_sock, 2) != 2)*/ ) {
      /* a partire da questo punto non si dispone di uno stderr dove *
       * scrivere messaggi d'errore, percio' si esce senza avvisi    */
      //err(EXIT_FAILURE, "on dup2(%d, ..) for %s", com_sock, service_to_str(serv));
      exit(EXIT_FAILURE);
    }

    /* chiusura di tutti i file descriptor indesiderati */
    close_socks(head);

    /* esecuzione del server tramite execvp(), che passa automaticamente     *
     * l'environment al nuovo programma e cerca il file da eseguire nelle    *
     * directory predefinite nel caso in cui non si trovi in quella corrente */
    execvp(serv->server_path, serv->args);
    if (errno != 0) {
      /* stderr non disponibile */
      //err(EXIT_FAILURE, "on exec(%s, ..) for %s", serv->server_path, service_to_str(serv));
      exit(EXIT_FAILURE);
    }

    /* questo punto di uscita dovrebbe essere irraggiungibile */
    exit(EXIT_FAILURE);
  }
  free(serv_str);
  return;
}


/**
 *  CLOSE SOCKETS
**/
void close_socks(service_t* head) {
  service_t* serv;
  for (serv = head; serv != NULL; serv = serv->next) {
    if (serv->sock > 2) {
      close(serv->sock);
    }
  }
  return;
}

